package br.edu.ufcg.splab.simulator;



import java.util.HashMap;
import java.util.List;
import java.util.Map;

import br.edu.ufcg.splab.core.InterfaceEdge;
import br.edu.ufcg.splab.core.InterfaceVertex;
import br.edu.ufcg.splab.core.edges.EdgeType;
import br.edu.ufcg.splab.core.graph.Graph;
import br.edu.ufcg.splab.core.vertex.Vertex;
import br.edu.ufcg.splab.exceptions.LTSBTException;

/**
 * 
 * @author Mayza Nunes
 * 
 * <P> A class that generates the graph dynamically.
 *
 * Is responsible for forming the graph, adding paths, 
 * {@link br.edu.ufcg.splab.simulator.StructuralPattern#loop loops},
 * {@link br.edu.ufcg.splab.simulator.StructuralPattern#join joins}. 
 * In addition to naming the edges and vertices of the graph.
 * 
 */

public class LTSGenerator {
	private Graph graph;
	private Integer controlLabel;
	final String[] alphabet = { "A", "B", "C", "D", "E", "F", "G", "H", "I",
			"J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
			"W", "X", "Y", "Z" };
	private Map<InterfaceVertex,Integer> levels;
	private Integer vertexLabel;
	private String edgeLabel ;
	private EdgeType type;

/**
 * The constructor is responsible for initializing the control variables,  
 * create the root of the graph and register the root on the states of the graph.
 *  
 * VertexLabel is responsible for control the label of vertex,that is named with numbers.
 * ControlLabel is responsible for control the position of the list alphabet,to put the edge label.
 * Edge label is responsible to retain the label of the edge,thats is defined with the position of alphabet
 * 
 */

	public LTSGenerator() {
		this.vertexLabel = 0;
		this.controlLabel = -1;
		edgeLabel = "";
		this.graph = new Graph();
		this.setLevels(new HashMap<InterfaceVertex,Integer>());
		InterfaceVertex vertexRoot = new Vertex(this.vertexLabel.toString());
		this.graph.setRoot(vertexRoot);
		registerVertex(vertexRoot, vertexLabel);
		this.type = EdgeType.DEFAULT;
	}
	
	/**
	 * Add a path in the graph. The method receives the source vertex, create the sink vertex 
	 * and connect the points.
	 * 
	 * @param vertexFrom
	 *            Source vertex
	 * @throws LTSBTException 
	 * 
	 */
	
	 public String addPath(InterfaceVertex vertexFrom) throws LTSBTException{	 
		if (this.existingNodo(vertexFrom)) {
			InterfaceVertex vertexTo = creatVertexTo(vertexFrom);
			connectPoints(vertexFrom, vertexTo);
			return vertexTo.toString();
		} else
			throw new LTSBTException();
	}
	

	/**
	 * Add a loop in the graph. The method receives the source vertex, the sink vertex,and connect the two.
	 * For this is necessary that the level of the sink vertex is biggest than source vertex.
	 * @param from
	 *            Source vertex
	 * @param to
	 *            Sink vertex
	 * @throws Exception
	 * 			Throws exception if the source vertex does not exist in the graph
	 */
	
	 
public void addLoop(InterfaceVertex from, InterfaceVertex to) throws LTSBTException   {
		if ((this.existingNodo(from)) && (this.existingNodo(to))) {
			controlLabel++;
			connectPoints(from, to);	
		} else{
			String nodes = "";
			int nodeNumber = 0;
			if(!this.existingNodo(from)){
				nodes += from.getLabel();
				nodeNumber++;
			}
			if(!this.existingNodo(to)){
				nodes += " and " + to.getLabel();
				nodeNumber++;
			}
			throw new LTSBTException("The node" + (nodeNumber > 1 ? "s " : " " ) + nodes + (nodeNumber > 1 ? "do " : "does " ) + " not exist in the graph");
		}
	}

	/**
	 * Add a branch in the graph. 
	 * The method receives the source vertex,created two children for the source vertices and connect points.
	 * 
	 * @param from
	 *            Source Branch
	 * @throws LTSBTException 
	 */
	public void addBranch(InterfaceVertex from) throws LTSBTException{
		if (this.existingNodo(from)) {
			addPath(from);
			addPath(from);
		}
		else
			throw new LTSBTException("The node " + from.getLabel() + " does not exist in the graph");
	}

	/**
	 *  Add a join in the graph.
	 *  The method receives the two source vertices,create the sink vertex,
	 *  calculate the level of the state considering the longest path and connect the two 
	 *  vertices received with the sink vertex created. 
	 * 
	 * @param first
	 * 				first vertex of a join
	 * @param second
	 *              second vertex of a join
	 * @throws LTSBTException 
	 * @throws Exception
	 * 			Throws exception if the one or two sources vertices does not exist in the graph
	 */
	public void addJoin(InterfaceVertex first, InterfaceVertex second) throws LTSBTException{
		updatePath();
		InterfaceVertex vertexTo = new Vertex(this.vertexLabel.toString());
		if(levels.get(first)+1 > levels.get(second)+1){
			registerVertex(vertexTo, levels.get(first)+1);	
		}else{
			registerVertex(vertexTo, levels.get(second)+1);	
		}
		this.graph.getStates().put(vertexTo.getUUID(), vertexTo);

		if (this.existingNodo(first) && this.existingNodo(second)) {
			connectPoints(first, vertexTo);
			controlLabel++;
			connectPoints(second, vertexTo);
		}
		else{
			String nodes = "";
			int nodeNumber = 0;
			if(!this.existingNodo(first)){
				nodes += first.getLabel();
				nodeNumber++;
			}
			if(!this.existingNodo(second)){
				nodes += " and " + second.getLabel();
				nodeNumber++;
			}
			throw new LTSBTException("The node" + (nodeNumber > 1 ? "s " : " " ) + nodes + (nodeNumber > 1 ? "do " : "does " ) + " not exist in the graph");
		}
	}

	/**
	 * Tests if the vertex exists in the graph
	 * 
	 * @param vertex
	 * 			vertex to check
	 * @return 
	 * 		True if the vertex exist and False if not.
	 */
	private boolean existingNodo(InterfaceVertex vertex) {
		if (this.graph.getStates().containsKey(vertex.getUUID()) ) {
			return true;
		} else
			return false;
	}

	/**
	 * Get the graph
	 * @return the graph
	 */
	public Graph getGraphGenerator() {
		return this.graph;
	}

	/**
	 * Get  the map, where the key are the vertex and values are the levels of these
	 * @return the map
	 */
	
	public Map<InterfaceVertex, Integer> getLevels(){
		return levels;
	}

	/**
	 * Set level of a vertex
	 * @param level
	 * 			the map, where the key are the vertex and values are the levels of these
	 * 			
	 */
	public void setLevels(Map<InterfaceVertex,Integer> level) {
		this.levels = level;
	}

	/**
	 * Register the vertex in the graph maps and register the level of the vertex in the map levels
	 * @param vertex
	 * 			vertex to register
	 * @param level
	 * 			level of a vertex
	 */
	protected void registerVertex(InterfaceVertex vertex, int level){
		graph.getStates().put(vertex.getUUID(), vertex);
		levels.put(vertex, level);
	}

	/**
	 * Create the label of the edge,based on the current value of control label, that is the position of a list alphabet.
	 * When using all the letters of the alphabet,the edge label is added with more characters,like AA,after AAA and so forth.
	 * 
	 * @return label
	 * 			label of the edge
	 */
	protected String getAutomaticLabel() {
		if (controlLabel < 26) {
			return this.edgeLabel + this.alphabet[controlLabel];

		} else {
			controlLabel = 0;
			this.edgeLabel += this.alphabet[controlLabel];   
			return getAutomaticLabel();
		}  
	}
	/**
	 * Updates variables of  control the labels of vertices and edges
	 */
	protected void updatePath(){
		this.controlLabel++;
		this.vertexLabel++;	
	}

	/**
	 * The method receives the source vertex,create and register the sink vertex in the graph
	 *
	 * @param vertexFrom
	 * 				source vertex
	 * @return vertexTo,the sink vertex
	 */
	protected InterfaceVertex creatVertexTo(InterfaceVertex vertexFrom){
		updatePath();
		String vertexToLabel = this.vertexLabel.toString();
		InterfaceVertex vertexTo = new Vertex(vertexToLabel);
		registerVertex(vertexTo, levels.get(vertexFrom)+1);
		return vertexTo;
	}
	
	

	/**
	 * It is responsible to connect two vertices,in other words,create one transition and connect 
	 * the two vertex of the entry method
	 * 
	 * @param vertexFrom
	 * 			source vertex
	 * @param vertexTo
	 * 			destination vertex
	 * @throws Exception
	 */
	protected void connectPoints(InterfaceVertex vertexFrom, InterfaceVertex vertexTo) throws LTSBTException{
		this.graph.createEdge(vertexFrom.getUUID(), vertexTo.getUUID(), getAutomaticLabel() , type);	
	}

	/**
	 * Update a list of the vertices of the graph that are possible destinations for the loop from vertex. 
	 * The method receives the source vertex and one list of InterfaceVertex to be updated with the vertices before the vertex source.
	 * The list must contain all vertices before the vertex source excluding the root vertex.
	 * 
	 * @param vertex
	 * 			source vertex of the loop
	 * @param visitados
	 * 			list of vertex that are before the source vertex
	 */
	public void getPossibleLoop(InterfaceVertex vertex,List<InterfaceVertex> visitados){
		for(InterfaceEdge edge : vertex.getInTransitions()){
			if(!visitados.contains(edge.getFrom()) && 
					edge.getFrom().getInTransitions().size()!=0 && 
					getLevels().get(vertex) > getLevels().get(edge.getFrom())
					){
				visitados.add(edge.getFrom());
				getPossibleLoop(edge.getFrom(), visitados);
			}	
		}
	}
}


